from multiprocessing import Process, Queue
import threading
import sys
import time
import os

# import hmi_tjc as tjc
import hmi_qt  as hmi
from PyQt5.QtWidgets import QApplication
import mc_sm2 as sm2
import log as logger
import update

MC_VERSION="4.3.1"
EXCTR = "3DP"
EXTEND = "On"

DEFAULt_BIN_NAME = "SM2_MC_APP_V4.3.1_20210831.bin"

TARGET_E_TEMP=80
TARGET_B_TEMP=40

KEYWORD_VER     = "SM2-"
KEYWORD_FAULT   = "Fault: "
KEYWORD_EXCTR   = "Tool Head: "
KEYWORD_CHAMBER = "Enclosure online: On"
KEYWORD_E_TEMP  = " B:"
KEYWORD_B_TEMP  = " @:"

FAULT_FLAG_NO_EXECUTOR          = 1
FAULT_FLAG_NO_LINEAR            = (1<<1)
FAULT_FLAG_BED_PORT             = (1<<2)
FAULT_FLAG_FILAMENT	            = (1<<3)
FAULT_FLAG_LOST_SETTING	        = (1<<4)
FAULT_FLAG_LOST_EXECUTOR        = (1<<5)
FAULT_FLAG_POWER_LOSS	        = (1<<6)
FAULT_FLAG_HOTEND_HEATFAIL      = (1<<7)
FAULT_FLAG_BED_HEATFAIL         = (1<<8)
FAULT_FLAG_HOTEND_RUNWAWY       = (1<<9)
FAULT_FLAG_BED_RUNAWAY          = (1<<10)
FAULT_FLAG_HOTEND_SENSOR_BAD    = (1<<11)
FAULT_FLAG_BED_SENSOR_BAD       = (1<<12)
FAULT_FLAG_LOST_LINEAR          = (1<<13)
FAULT_FLAG_HOTEND_MAXTEMP       = (1<<14)
FAULT_FLAG_BED_MAXTEMP          = (1<<15)
FAULT_FLAG_HOTEND_SHORTCIRCUIT  = (1<<16)
FAULT_FLAG_BED_SHORTCIRCUIT     = (1<<17)
FAULT_FLAG_HOTEND_SENSOR_COMEOFF = (1<<18)
FAULT_FLAG_BED_SENSOR_COMEOFF   = (1<<19)
FAULT_FLAG_UNKNOW_MODEL         = (1<<20)
FAULT_FLAG_DOOR_OPENED          = (1<<21)
FAULT_FLAG_UNKNOW               = (1<<31)

# command PDU
# [command, channel, parameters]

def mc_upgrade(mc, q):
    def progress(percent):
        q.put([hmi.CMD_UPDATE_MC, mc.ch(), percent])
    upgrade = update.MasterContorlUpdate(mc)
    if not upgrade.update(DEFAULt_BIN_NAME, progress):
        sm2.UpgradeException("ch[{0}]: ".format(mc.ch()) + "failed upgrade test MC")
    mc.waiting_bootup()

def mc_check_ver_and_upgrade(mc, q):
    ret = mc.send_cmd(sm2.GET_VER)
    if ret[0] <= 0:
        raise sm2.DisconnectedException("ch[{0}]: ".format(mc.ch()) + "No ack from MC")

    ver_info = ret[1]
    if KEYWORD_VER not in ver_info:
        raise sm2.TestVersionException("ch[{0}]: ".format(mc.ch()) + "No version str")
    else:
        ver_start = ver_info.find(KEYWORD_VER) + len(KEYWORD_VER)
        mc.log_d("version start index: {0}".format(ver_start))

    ver_end = ver_start
    for i in range(ver_start, ret[0]):
        if ver_info[i] == '\n':
            ver_end = i
            break

    mc.log_d("version end index: {0}".format(ver_end))

    ver = ver_info[ver_start : ver_end]

    if MC_VERSION not in ver:
        mc_upgrade(mc, q)

def mc_check_ver(mc, q):
    ret = mc.send_cmd(sm2.GET_VER)
    if ret[0] <= 0:
        raise sm2.DisconnectedException("ch[{0}]: ".format(mc.ch()) + "No ack from MC")

    ver_info = ret[1]
    if KEYWORD_VER not in ver_info:
        raise sm2.VersionException("ch[{0}]: ".format(mc.ch()) + "No version str")
    else:
        ver_start = ver_info.find(KEYWORD_VER) + len(KEYWORD_VER)
        mc.log_d("version start index: {0}".format(ver_start))

    ver_end = ver_start
    for i in range(ver_start, ret[0]):
        if ver_info[i] == '\n':
            ver_end = i
            break

    mc.log_d("version end index: {0}".format(ver_end))

    ver = ver_info[ver_start : ver_end]

    if MC_VERSION not in ver:
        mc.log_i("Got version {0} not match {1}".format(ver, MC_VERSION))
        raise sm2.VersionException("Error Version")

def remap_8pin_to_addon(mc, q):
    q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_REMAP_ADDON])
    ret = mc.send_cmd(sm2.REMAP_ADDON_PORT)
    if ret[0] <= 0:
        raise sm2.DisconnectedException("ch[{0}]: ".format(mc.ch()) + "No ack from remap addon")
    ret = mc.send_cmd(sm2.SAVE_SETTINGS)
    if ret[0] <= 0:
        raise sm2.DisconnectedException("ch[{0}]: ".format(mc.ch()) + "No ack from save setting")
    ret = mc.send_cmd(sm2.RESET_MACHINE)
    if ret[0] <= 0:
        raise sm2.DisconnectedException("ch[{0}]: ".format(mc.ch()) + "No ack from reboot")
    mc.waiting_bootup()

def remap_8pin_to_default(mc, q):
    q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_REMAP_DEFAULT])
    ret = mc.send_cmd(sm2.REMAP_DEFAULT_PORT)
    if ret[0] <= 0:
        raise sm2.DisconnectedException("ch[{0}]: ".format(mc.ch()) + "No ack from remap default")
    ret = mc.send_cmd(sm2.SAVE_SETTINGS)
    if ret[0] <= 0:
        raise sm2.DisconnectedException("ch[{0}]: ".format(mc.ch()) + "No ack from save setting")
    ret = mc.send_cmd(sm2.RESET_MACHINE)
    if ret[0] <= 0:
        raise sm2.DisconnectedException("ch[{0}]: ".format(mc.ch()) + "No ack from reboot")
    mc.waiting_bootup()

def mc_check_fault(mc, q):
    ret = mc.send_cmd(sm2.GET_FAULT)
    if ret[0] <= 0:
        raise sm2.DisconnectedException("ch[{0}]: ".format(mc.ch()) + "No ack from MC")

    # normally, new version should have debug info
    dbg_info = ret[1]
    if KEYWORD_FAULT not in dbg_info:
        raise sm2.VersionException("ch[{0}]: ".format(mc.ch()) + "No version str")
    else:
        fault_start = dbg_info.find(KEYWORD_FAULT) + len(KEYWORD_FAULT)
        mc.log_d("fault start index: {0}".format(fault_start))

    fault_end = fault_start
    for i in range(fault_start, ret[0]):
        if dbg_info[i] == ",":
            fault_end = i
            break

    mc.log_d("fault end index: {0}".format(fault_end))
    fault_str = dbg_info[fault_start : fault_end]

    fault_num = int(fault_str, 16)
    mc.log_i("fault num: " + hex(fault_num))
    mc.log_i("exctr flag: " + hex(FAULT_FLAG_NO_EXECUTOR))
    mc.log_i("bed port  flag: " + hex(FAULT_FLAG_BED_PORT))

    if not (fault_num & FAULT_FLAG_NO_EXECUTOR):
        mc.log_i("Executor is exist")
    else:
        raise sm2.NoExecutorException("ch[{0}]: ".format(mc.ch()) + "No Executor")

    if not (fault_num & FAULT_FLAG_NO_LINEAR):
        mc.log_i("Linear is exist")
    else:
        raise sm2.NoLinearException("ch[{0}]: ".format(mc.ch()) + "No Linear")

    if not (fault_num & FAULT_FLAG_UNKNOW_MODEL):
        mc.log_i("Model is confirmed")
    else:
        raise sm2.NoModelException("ch[{0}]: ".format(mc.ch()) + "Unknown Model")

    if not (fault_num & FAULT_FLAG_BED_PORT):
        mc.log_i("Bed port is good")
    else:
        raise sm2.BedPortException("Bed port is damaged")

    if not (fault_num & FAULT_FLAG_LOST_SETTING):
        mc.log_i("settings have been saved")
    else:
        mc.log_i("settings need to be saved")
        mc.send_cmd(sm2.SAVE_SETTINGS)

def mc_check_usb(mc, q):
    if os.path.exists("/dev/sda"):
        mc.log_i("detected usb")
        return
    else:
        raise sm2.NoUSBstickException("ch[{0}]: ".format(mc.ch()) + "cannot detect USB stick")

def mc_check_chamber(mc, q):
    ret = mc.send_cmd(sm2.GET_CHAMBER)
    if ret[0] <= 0:
        raise sm2.DisconnectedException("ch[{0}]: ".format(mc.ch()) + "No ack from MC")

    chamber_info = ret[1]
    if KEYWORD_CHAMBER not in chamber_info:
        raise sm2.NoChamberException("ch[{0}]: ".format(mc.ch()) + "No chamber str")

    mc.log_i("Got Chamber!")

def mc_check_executor(mc, q):
    ret = mc.send_cmd(sm2.GET_EXECUTOR)
    if ret[0] <= 0:
        raise sm2.DisconnectedException("ch[{0}]: ".format(mc.ch()) + "No ack from MC")

    exctr_info = ret[1]
    if KEYWORD_EXCTR not in exctr_info:
        raise sm2.ExecutorException("ch[{0}]: ".format(mc.ch()) + "No executor str")
    else:
        exctr_start = exctr_info.find(KEYWORD_EXCTR) + len(KEYWORD_EXCTR)
        mc.log_d("Executor start index: {0}".format(exctr_start))

    exctr_end = exctr_start
    for i in range(exctr_start, ret[0]):
        if exctr_info[i] == '\n':
            exctr_end = i
            break

    mc.log_d("Executor end index: {0}".format(exctr_end))

    exctr = exctr_info[exctr_start : exctr_end]
    mc.log_i("Got Executor: " + exctr)
    if EXCTR not in exctr:
        raise sm2.ExecutorException("ch[{0}]: ".format(mc.ch()) + "Executor error")


def mc_check_extend(mc, q):
    ret = mc.send_cmd(sm2.GET_EXTEND)
    if ret[0] <= 0:
        raise sm2.DisconnectedException("ch[{0}]: ".format(mc.ch()) + "No ack from MC")

    extend_info = ret[1]
    if KEYWORD_EXTEND not in extend_info:
        raise sm2.ExtendException("ch[{0}]: ".format(mc.ch()) + "No enclosure str")
    else:
        extend_start = extend_info.find(KEYWORD_EXTEND) + len(KEYWORD_EXTEND)
        mc.log_d("extend start index: {0}".format(extend_start))

    extend_end = extend_start
    for i in range(extend_start, ret[0]):
        if extend_info[i] == '\n':
            extend_end = i
            break

    mc.log_d("extend end index: {0}".format(extend_end))

    exctr = extend_info[extend_start: extend_end]
    mc.log_i("Got extend: " + exctr)
    if EXTEND not in exctr:
        raise sm2.ExtendException("ch[{0}]: ".format(mc.ch()) + "extend error")


def parse_temp(temp_info, keyword=None):
    """ parse the temperature from M105
    info: " T:0.00 /0.00 B:-14.84 /0.00 @:0 B@:0"
    """

    if keyword == None:
        return

    if keyword not in temp_info:
        raise sm2.MeasureTempException("ch[{0}]: ".format(mc.ch()) + "uncorrect temp string")

    temp_end = temp_info.find(keyword)

    temp_start = temp_end
    for i in range(temp_end, 0, -1):
        if temp_info[i] == '/':
            temp_start = i + 1
            break
    target = float(temp_info[temp_start : temp_end])

    temp_end = temp_start - 2
    for i in range(temp_end, 0, -1):
        if temp_info[i] == ':':
            temp_start = i + 1
            break
    current = float(temp_info[temp_start : temp_end])

    return [current, target]


def mc_check_cur_temp(mc, q, espec=[10, 200], bspec=[10, 60]):
    ret = mc.send_cmd(sm2.GET_TEMP)
    if ret[0] <= 0:
        raise sm2.DisconnectedException("ch[{0}]: ".format(mc.ch()) + "No ack from MC")

    etemp = parse_temp(ret[1], KEYWORD_E_TEMP)
    mc.log_i("current extrude temp: {0}".format(etemp[0]))
    q.put([hmi.CMD_UPDATE_TEMP, mc.ch(), hmi.TEMP_HOTEND, etemp[0]])

    btemp = parse_temp(ret[1], KEYWORD_B_TEMP)
    mc.log_i("current bed temp: {0}".format(btemp[0]))
    q.put([hmi.CMD_UPDATE_TEMP, mc.ch(), hmi.TEMP_BED, btemp[0]])

    ex_etemp = False
    ex_btemp = False

    if etemp[0] < espec[0] or etemp[0] > espec[1]:
        ex_etemp = True

    if btemp[0] < bspec[0] or btemp[0] > bspec[1]:
        ex_btemp = True

    if ex_etemp and ex_btemp:
        raise sm2.MeasureTempException("ch[{0}]: ".format(mc.ch()) + "Abnommal extrudor & bed temp")
    elif ex_etemp:
        raise sm2.MeasureExctrException("ch[{0}]: ".format(mc.ch()) + "Abnommal extrudor temp")
    elif ex_btemp:
        raise sm2.MeasureBedException("ch[{0}]: ".format(mc.ch()) + "Abnommal bed temp")


def mc_check_target_temp(mc, q, etarget=200, btarget=60):
    ret = mc.send_cmd(sm2.GET_TEMP)
    if ret[0] <= 0:
        raise sm2.DisconnectedException("ch[{0}]: ".format(mc.ch()) + "No ack from MC")

    etemp = parse_temp(ret[1], KEYWORD_E_TEMP)
    btemp = parse_temp(ret[1], KEYWORD_B_TEMP)

    mc.log_i("target extrude temp: {0}".format(etemp[1]))
    mc.log_i("target bed temp: {0}".format(btemp[1]))

    if etemp[1] != etarget:
        raise sm2.SetTempException("ch[{0}]: ".format(mc.ch()) + "uncorrect extrudor target temp")

    if btemp[1] != btarget:
        raise sm2.SetTempException("ch[{0}]: ".format(mc.ch()) + "uncorrect bed target temp")


def homing(mc, q):
    mc.send_cmd(sm2.HOME_ALL, False)
    timeout = 0

    q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_HOMING])

    while True:
        ret = mc.read_ack()
        if ret[0] > 0:
            if "Count" in ret[1]:
                break
        timeout += 1
        mc.log_i("waiting home: {0}s".format(timeout))
        if timeout > 20:
            mc.log_i("timeout to wating home over")
            break
        time.sleep(2)
    mc.send_cmd("G91", True)
    time.sleep(1)
    mc.send_cmd("G0 F3000 X-50 Y-50 Z-50", True)
    time.sleep(2)
    mc.log_i("home over")

def homing_YZ(mc, q):
    mc.send_cmd(sm2.HOME_YZ, False)
    timeout = 0

    q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_HOMING_ADDON])

    while True:
        ret = mc.read_ack()
        if ret[0] > 0:
            if "Count" in ret[1]:
                break
        timeout += 1
        mc.log_i("waiting home: {0}s".format(timeout))
        if timeout > 20:
            mc.log_i("timeout to wating home over")
            break
        time.sleep(2)
    mc.send_cmd("G91", True)
    time.sleep(1)
    mc.send_cmd("G0 F3000 Y-50 Z-50", True)
    time.sleep(2)
    mc.log_i("home over")

def mc_monitor_heating(mc, q, etarget=195, btarget=55):
    begin_heat = time.time()
    ecounter = 0
    bcounter = 0

    mc.log_i("monitor heating")

    while time.time() - begin_heat < 180:
        ret = mc.send_cmd(sm2.GET_TEMP)
        if ret[0] > 0:
            etemp = parse_temp(ret[1], KEYWORD_E_TEMP)
            btemp = parse_temp(ret[1], KEYWORD_B_TEMP)

            q.put([hmi.CMD_UPDATE_TEMP, mc.ch(), hmi.TEMP_HOTEND, etemp[0]])
            q.put([hmi.CMD_UPDATE_TEMP, mc.ch(), hmi.TEMP_BED, btemp[0]])

            if etemp[0] > etarget:
                ecounter += 1

            if btemp[0] > btarget:
                bcounter += 1
        else:
            mc.log_e("read temp failed")

        if ecounter > 1 and bcounter > 1:
            return
        time.sleep(3)

    if ecounter < 2 and bcounter < 2:
        raise sm2.HeatException("ch[{0}]: ".format(mc.ch()) + "both of them heated failed")
    elif ecounter < 2:
        raise sm2.HeatExctrException("ch[{0}]: ".format(mc.ch()) + "extrudor heated failed")
    elif bcounter < 2:
        raise sm2.HeatBedException("ch[{0}]: ".format(mc.ch()) + "bed heated failed")


def waiting_removed(mc, q):
    mc.disconnect()
    q.put([hmi.CMD_UPDATE_TEMP, mc.ch(), hmi.TEMP_HOTEND, 0])
    q.put([hmi.CMD_UPDATE_TEMP, mc.ch(), hmi.TEMP_BED, 0])

    while True:
        if os.path.exists(mc.port()):
            mc.log_d("waiting serial removed")
            time.sleep(3)
        else:
            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_UNCONNECTED])
            break

def mc_worker(mc, q):
    """ test procedure:
            0. connect to mc
            1. check version
            2. check fault bits
                2.1 if bed port is damaged
                2.2 if we got executor
                2.3 if any other fault happened except lost configuration
            3. check if executor is 3DP
            4. check if temperature is normal for hotend & bed
            5. start heating
            6. monitor if heating is timeout
            7. disconnect to mc
            8. tell HMI the test result
    """

    mc.log_i("process started")

    #mc.debug(new=True)

    while True:
        try:
            if os.path.exists(mc.port()):
                q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_CONNECTED])
                mc.connect()
            else:
                time.sleep(3)
                mc.log_d("waiting serial connected")
                continue

            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_TESTING])

            mc_check_ver_and_upgrade(mc, q)
            mc_check_ver(mc, q)
            remap_8pin_to_addon(mc, q)
            homing_YZ(mc, q)
            remap_8pin_to_default(mc, q)

            #mc_check_usb(mc, q)

            mc_check_fault(mc, q)

            mc_check_executor(mc, q)

            mc_check_chamber(mc, q)

            mc_check_cur_temp(mc, q)

            mc.log_i("start heating")
            mc.send_cmd(sm2.SET_TEMP_E + str(TARGET_E_TEMP))
            mc.send_cmd(sm2.SET_TEMP_B + str(TARGET_B_TEMP))

            mc_check_target_temp(mc, q, etarget=TARGET_E_TEMP, btarget=TARGET_B_TEMP)

            homing(mc, q)
            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_TESTING])

            mc_monitor_heating(mc, q, etarget=TARGET_E_TEMP-2, btarget=TARGET_B_TEMP-2)

            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_TEST_PASS])
            mc.log_i("Test pass")

            mc.send_cmd(sm2.SET_TEMP_E + "0")
            mc.send_cmd(sm2.SET_TEMP_B + "0")

            mc_check_fault(mc, q)

            waiting_removed(mc, q)

        except sm2.BootFailedException as e:
            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_NO_MC])
            mc.log_e("MC Error: {0}".format(e))
            waiting_removed(mc, q)
            continue
        except sm2.TestVersionException as e:
            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_VERSION_ERROR])
            mc.log_e("MC Error: {0}".format(e))
            waiting_removed(mc, q)
            continue
        except sm2.VersionException as e:
            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_VERSION_ERROR])
            mc.log_e("MC Error: {0}".format(e))
            waiting_removed(mc, q)
            continue

        except sm2.BedPortException as e:
            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_BAD_BED_PORT])
            mc.log_e("MC Error: {0}".format(e))
            waiting_removed(mc, q)
            continue

        except sm2.NoExecutorException as e:
            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_NO_EXECUTOR])
            mc.log_e("MC Error: {0}".format(e))
            waiting_removed(mc, q)
            continue

        except sm2.NoLinearException as e:
            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_NO_LINEAR])
            mc.log_e("MC Error: {0}".format(e))
            waiting_removed(mc, q)
            continue

        except sm2.NoModelException as e:
            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_NO_MODEL])
            mc.log_e("MC Error: {0}".format(e))
            waiting_removed(mc, q)
            continue

        except sm2.ExecutorException as e:
            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_EXCTR_TYPE_ERR])
            mc.log_e("MC Error: {0}".format(e))
            waiting_removed(mc, q)
            continue

        except sm2.ExtendException as e:
            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_NO_EXTEND])
            mc.log_e("MC Error: {0}".format(e))
            waiting_removed(mc, q)
            continue

        except sm2.MeasureTempException as e:
            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_MEASURE_TEMP_ERR])
            mc.log_e("MC Error: {0}".format(e))
            waiting_removed(mc, q)
            continue

        except sm2.MeasureBedException as e:
            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_MEASURE_BED_ERR])
            mc.log_e("MC Error: {0}".format(e))
            waiting_removed(mc, q)
            continue

        except sm2.MeasureExctrException as e:
            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_MEASURE_EXCTR_ERR])
            mc.log_e("MC Error: {0}".format(e))
            waiting_removed(mc, q)
            continue

        except sm2.HeatBedException as e:
            mc.send_cmd(sm2.SET_TEMP_E + "0")
            mc.send_cmd(sm2.SET_TEMP_B + "0")
            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_BED_HEATED_FAIL])
            mc.log_e("MC Error: {0}".format(e))
            waiting_removed(mc, q)
            continue

        except sm2.HeatExctrException as e:
            mc.send_cmd(sm2.SET_TEMP_E + "0")
            mc.send_cmd(sm2.SET_TEMP_B + "0")
            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_EXCTR_HEATED_FAIL])
            mc.log_e("MC Error: {0}".format(e))
            waiting_removed(mc, q)
            continue

        except sm2.HeatException as e:
            mc.send_cmd(sm2.SET_TEMP_E + "0")
            mc.send_cmd(sm2.SET_TEMP_B + "0")
            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_EXCTR_HEATED_FAIL])
            mc.log_e("MC Error: {0}".format(e))
            waiting_removed(mc, q)
            continue

        except sm2.SetTempException as e:
            mc.send_cmd(sm2.SET_TEMP_E + "0")
            mc.send_cmd(sm2.SET_TEMP_B + "0")
            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_SET_TEMP_ERR])
            mc.log_e("MC Error: {0}".format(e))
            waiting_removed(mc, q)
            continue

        except sm2.DisconnectedException as e:
            time.sleep(1)
            if not os.path.exists(mc.port()):
                mc.log_e("{0} is removed: {1}".format(mc.port(), e))
            else:
                mc.log_e("{0} is exist, but cannot operation: {1}".format(mc.port(), e))
            waiting_removed(mc, q)
            continue

        except sm2.NoUSBstickException as e:
            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_NO_USB_STICK])
            mc.log_e("MC Error: {0}".format(e))
            waiting_removed(mc, q)
            continue

        except sm2.NoChamberException as e:
            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_NO_CHAMBER])
            mc.log_e("MC Error: {0}".format(e))
            waiting_removed(mc, q)
            continue

        except sm2.UpgradeException as e:
            q.put([hmi.CMD_UPDATE_STA, mc.ch(), hmi.STA_FAIL_UPDATE_MC])
            mc.log_e("Upgrade MC Error: {0}".format(e))
            waiting_removed(mc, q)
            continue

        except Exception as e:
            mc.log_e("unexpected err: {0}".format(e))
            waiting_removed(mc, q)


def hmi_worker(ui, q, log):
    log.I("HMI: thread started")
    try:
        while True:
            msg = q.get()
            log.D("HMI: got message: {0}".format(msg))

            if int(msg[0]) == hmi.CMD_UPDATE_STA:
                log.D("HMI: update ch[{0}] status".format(msg[1]))
                ui.update_sta(ch=msg[1], sta=msg[2])
            elif int(msg[0]) == hmi.CMD_UPDATE_TEMP:
                log.D("HMI: update ch[{0}] temp".format(msg[1]))
                ui.update_temp(ch=msg[1], target=msg[2], temp=msg[3])
            elif int(msg[0]) == hmi.CMD_UPDATE_MC:
                log.D("HMI: update ch[{0}] mc".format(msg[1]))
                ui.update_mc(ch=msg[1], percent=msg[2])
            else:
                log.E("HMI: unknow command: ", msg)

    except Exception as e:
        log.E("HMI: unexpected error: {0}".format(e))


def main():
    try:
        curdir = sys.path[0]
        logdir =  os.path.join(curdir, "..")
        log = logger.Log(logdir)

        log.I("\n")
        log.I("NEW test start++++++++++++++++++++++++++++")
        log.I("\n")

        # create instance of main control
        ch_path = ["/dev/usbserial{0}".format(x) for x in range(0, 3)]
        mc_instance = [sm2.McSm2(port=ch_path[x], ch=x, logger=log) for x in range(0, 3)]

        app = QApplication(sys.argv)
        ui = hmi.HmiQt()
        ui.show()
        q_hmi = Queue()

        process_mc = [Process(target=mc_worker, name="mc{0}".format(x), args=(mc_instance[x], q_hmi)) for x in range(0, 3)]

        thread_hmi = threading.Thread(target=hmi_worker, name="ui_thread", args=(ui, q_hmi, log))

        for p in process_mc:
            p.start()

        thread_hmi.start()

        sys.exit(app.exec_())

    except Exception as e:
        log.E("MAIN: failed to create process: {0}".format(e))
        return

    # while True:
    #     try:
    #         for p in process_mc:
    #             if not p.is_alive():
    #                 log.E("MAIN: process {0} is dead".format(p.name))
    #                 q_hmi.put(hmi.CMD_UPDATE_STA, p._args[0].ch(), hmi.STA_PROCESS_DEAD)
    #         time.sleep(10)
    #     except Exception as e:
    #         log.E("MAIN: Error: {0}".format(e))
    #         for mc in mc_instance:
    #             mc.disconnect()
    #             del mc

    #         for p in process_mc:
    #             p.terminate()
    #         thread_hmi.terminate()

    #         return

if __name__ == '__main__':
    main()
